#!/usr/bin/env python
# coding: utf-8
"""
This file is part of pyfile2object.

Copyright 2010 Pedro Ignacio Guridi <pedro.guridi@gmail.com>

Foobar is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser Public License for more details.

You should have received a copy of the GNU Lesser Public License
along with pyfile2object.  If not, see <http://www.gnu.org/licenses/>.
"""

from template import ImportTemplate
import os
import xml.dom.minidom as minidom


class Reader:

	def __init__(self, input_file, mapping_file_path, mapped_class):
		self.__input_file = input_file
		self.__file_stream = None
		self.__errors = []
		self.__types_casting_map = { "int" : self._intType, "float" : self._floatType,
								"str" : self._strType }

		self.__mapped_class = mapped_class
		self.__exclude_line_prefix = None
		self.__counter = 0

		self.__config(mapping_file_path)

		self.open()

	def __get_entity_instace(self):
		"""
		This method returns an object from the mapped class
		"""
		try:
			return apply(self.__mapped_class, [])
		except Exception, e:
			print "Error while trying to create entity instance"
			raise e

	def __config(self, mapping_file_path):
		"""
		This method configures the library.
		"""
		self.__read_mapping_file(mapping_file_path)

	def __read_mapping_file(self, mapping_file_path):
		"""
		This method reads and parse the XML mapping file
		"""
		if not os.path.exists(mapping_file_path):
			raise Exception("Mapping file does not exists")
		
		mappingFile = None
		try:
			mappingFile = open(mapping_file_path, "r")
			mappingXmlString = mappingFile.read()
			mappingXml = minidom.parseString(mappingXmlString)

			for clazz in mappingXml.getElementsByTagName("class"):
				self.__exclude_line_prefix = clazz.getAttribute("exclude")
				self.__importTemplate = ImportTemplate(clazz.getAttribute("name"))
				self.__importTemplate.setExcludeLinePrefix(self.__exclude_line_prefix)
				
				clazz_obj = self.__get_entity_instace()
				for prop in clazz.getElementsByTagName("property"):
					propName = prop.getAttribute("name")
					if not hasattr(clazz_obj, str(propName).lower()):
						raise Exception("Class not properly mapped")
					self.__importTemplate.addField(propName, prop.getAttribute("start_column"), prop.getAttribute("end_column"),
										str(propName).lower(), prop.getAttribute("type"))
		except Exception, e:
			raise e
		finally:
			if mappingFile:
				mappingFile.close()
			
	def open(self):
		"""
		This method opens the file stream
		"""
		try:
			self.__file_stream = open(self.__input_file, "r")
		except IOError  as e:
			raise e

	def read_line(self):
		"""
		This method iterates over the file returning objects
		"""
		for line in self.__file_stream:
			if not line:
				self.close()
				raise StopIteration
			if line[0] == self.__exclude_line_prefix:
				continue
			self.__counter += 1
			try:
				yield self.__populate_entity_obj(self.__get_entity_instace(), line)
			except Exception, e:
				self.__errors.append(["Error in line: " + str(self.__counter), e])

	def __populate_entity_obj(self, entity, line):
		"""
		This method creates the object and maps the attributes
		"""
		for prop in self.__importTemplate.getFields():
			try:
				value = self.__types_casting_map[prop["type"]](line[prop["start_column"]:prop["end_column"]])
				setattr(entity, prop["setter"], value)
			except Exception, e:
				raise e
		return entity

	def close(self):
		"""
		This method closes the file stream
		"""
		if self.__file_stream:
			self.__file_stream.close()

	def get_count(self):
		"""
		This method returns the total items read
		"""
		return self.__counter
	
	def get_errors(self):
		return self.__errors
	
	
	"""
	
	Type management and casting functions
	
	"""
	def _intType(self, string):
		return int(string)
	
	def _floatType(self, string):
		return float(string)
	
	def _strType(self, string):
		return string
